import { dirname, extname, isAbsolute, join } from 'path'
import { existsSync, readFileSync } from 'fs-extra'
import { runInThisContext } from 'vm'
import { DependencyContainer, container } from 'tsyringe'
import { NotFoundError } from './error';
import { ENV, ROOT } from './tokens';
const fileCache: Map<string, any> = new Map();
const wrapperFnCache: Map<string, any> = new Map();
export class Module {
    extname: string;
    exports: object = {};
    __dirname: string;
    __filename: string;
    root: string;
    env: any;
    constructor(public id: string, public injector: DependencyContainer) {
        this.extname = extname(id)
        this.__filename = id;
        this.__dirname = dirname(id)
        this.root = container.resolve(ROOT)
        this.env = container.resolve(ENV)
    }
    async load() {
        const loader = Module.extensions.find(it => it.extname === this.extname)
        if (loader) loader.load(this)
    }
    static extensions: ModuleLoader[] = [{
        extname: '.js',
        load: (module: Module) => {
            const isDev = !!module.env.dev
            const _wrapperFn = wrapperFnCache.get(module.id)
            if (_wrapperFn && !isDev) {
                _wrapperFn.call(module.exports, module.exports, skerReq([module.__dirname], module.injector), module, module.__filename, module.__dirname, module.injector);
                return;
            }
            // 开发模式
            const content = readFileSync(module.id, 'utf8');
            const code = `(function(exports, require, module, __filename,__dirname, injector){${content}})`
            const wrapperFn = runInThisContext(code, {
                filename: module.__filename,
                lineOffset: 0,
                displayErrors: true
            });
            wrapperFnCache.set(module.id, wrapperFn)
            wrapperFn.call(module.exports, module.exports, skerReq([module.__dirname], module.injector), module, module.__filename, module.__dirname, module.injector);
        }
    }, {
        extname: '.json',
        load: (module: Module) => {
            if (fileCache.has(module.id)) {
                module.exports = fileCache.get(module.id)
                return;
            }
            const content = readFileSync(module.id, 'utf8');
            const json = JSON.parse(content)
            fileCache.set(module.id, json)
            module.exports = json;
        }
    }];

    static clear() {
        fileCache.clear();
        wrapperFnCache.clear();
    }
}

export interface ModuleLoader {
    extname: string;
    load<T>(module: Module): void;
}
export const skerReq = (paths: string[], injector: DependencyContainer) => (id: string) => {
    let absolutePath: string | null = null;
    const root = container.resolve(ROOT)
    try {
        absolutePath = require.resolve(id, {
            paths: [...paths]
        });
    } catch (e) {
        throw new NotFoundError(absolutePath)
    }
    if (!absolutePath) throw new NotFoundError(absolutePath)
    let isRuntime = absolutePath.includes(join('@sker', 'runtime')) || absolutePath.includes('@sker/runtime') || absolutePath.startsWith(join(root, 'addons'))
    if (!isRuntime) {
        return require(absolutePath);
    }
    const module = new Module(absolutePath, injector);
    module.load();
    return module.exports;
}
